import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;


public class JdbcUtil {
    //链接数据库需要传入这些参数，我们先声明出来
    private static String URL = "jdbc:mysql:///mybatis_demo?characterEncoding=utf8";
    private static String USERNAME = "root";
    private static String PASSWORD = "123456";
    private static Connection conn = null;

    //然后我们写一个链接方法用来链接数据库、返回的是一个链接对象
    public static Connection getConnection() {
        //这里是Connection的引用声明，因为要返回给调用者，所以不能在try里声明
        if(conn != null){
            return conn;
        }
        try {
            Class.forName("com.mysql.jdbc.Driver");
            //DriverManager:提供管理一组 JDBC 驱动程序的基本服务。通过它来使用驱动类
            conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

    public static <T>
    List<T> query(Class<T> clazz, String sql, Object...data){
        List list = new ArrayList();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //调用链接方法获取与数据库的链接
            conn = getConnection();

            //利用传入的sql语句创建一个pstmt对象，以便于我们把传入的数组里的数据放到sql语句中
            pstmt = conn.prepareStatement(sql);
            if (data != null){
                for (int i = 0; i < data.length; i++) {
                    pstmt.setObject(i+1, data[i]);
                }
            }
            //通过executeQuery方法执行最终的sql语句，获取到结果集rs对象
            rs = pstmt.executeQuery();
            //通过next方法遍历rs
            while(rs.next()){
                ResultSetMetaData metaData = rs.getMetaData();//获取标的元数据，也就是 表的行
                T obj = clazz.newInstance();
                //一行有很多列，用for遍历列getColumnCont方法是获取有多少列的
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    //getColumnName（）方法，括号是从1开始的列，每次循环i++，这个方法是获取下标所对应的列名
                    String columnName = metaData.getColumnName(i+1);//元数据metadate中的数据也是从1开始，不要忘i是0所以要加一
                    Object object = rs.getObject(columnName);//根据列明从rs结果集中获取数据
                    Field field = clazz.getDeclaredField(columnName);//根据列明获取传入的类的属性的字段，也就是获取属性名
                    field.setAccessible(true);//更改访问模式 ，强制访问私有属性
                    field.set(obj, object);//将数据写入字段，obj实际上就是传入的类的class的实例对象，就是这个 T obj = clazz.newInstance();
                }
                list.add(obj);
            }
            rs.close();
            pstmt.close();
        } catch (SQLException | InstantiationException | IllegalAccessException | NoSuchFieldException e) {
            e.printStackTrace();
        }
        return list;
    }

    public static <T>
    List<Map<String, Object>> query(String sql, Object...data){
        List<Map<String, Object>> list = new ArrayList();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            //调用链接方法获取与数据库的链接
            conn = getConnection();
            //利用传入的sql语句创建一个pstmt对象，以便于我们把传入的数组里的数据放到sql语句中
            pstmt = conn.prepareStatement(sql);
            if (data != null){
                for (int i = 0; i < data.length; i++) {
                    pstmt.setObject(i+1, data[i]);
                }
            }
            //通过executeQuery方法执行最终的sql语句，获取到结果集rs对象
            rs = pstmt.executeQuery();
            //通过next方法遍历rs
            while(rs.next()){
                ResultSetMetaData metaData = rs.getMetaData();//获取标的元数据，也就是 表的行

                Map<String, Object> item = new HashMap<>();
                //一行有很多列，用for遍历列getColumnCont方法是获取有多少列的
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    //getColumnName（）方法，括号是从1开始的列，每次循环i++，这个方法是获取下标所对应的列名
                    String columnName = metaData.getColumnName(i+1);//元数据metadate中的数据也是从1开始，不要忘i是0所以要加一
                    Object object = rs.getObject(columnName);//根据列明从rs结果集中获取数据
                    item.put(columnName, object);
                }
                list.add(item);
            }
            rs.close();
            pstmt.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    public static <T>
    boolean save(T obj) throws SQLException {
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        String class_name = clazz.getName();
        String sql = "insert into " + class_name.toLowerCase(Locale.ROOT) + " set ";

        List<Object> param = new ArrayList<>();

        for (Field f : fields){
            String f_name = f.getName();
            f.setAccessible(true);
            Object val = null;
            try {
                val = f.get(obj);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if(val == null){
                continue;
            }
            param.add(val);
            sql += f_name + "=? , ";
        }

        sql = sql.substring(0, sql.lastIndexOf(','));

        Connection conn = null;
        PreparedStatement pstmt = null;
        boolean res = true;
        int row = 0;
        try {
            //调用链接方法获取与数据库的链接
            conn = getConnection();
            conn.setAutoCommit(false);
            //利用传入的sql语句创建一个pstmt对象，以便于我们把传入的数组里的数据放到sql语句中
            pstmt = conn.prepareStatement(sql);

            if (!param.isEmpty()){
                for (int i = 0; i < param.size(); i++) {
                    pstmt.setObject(i+1, param.get(i));
                }
            }
            String final_sql = pstmt.toString();
            System.out.println(final_sql);

            res = pstmt.execute();

            if(!res) {
                row = pstmt.getUpdateCount();
            }
            conn.commit();
            pstmt.close();

        } catch (Exception e) {
            e.printStackTrace();
            conn.rollback();
        }

        return row > 0;
    }

    public static <T>
    void save(List<T> objs) throws SQLException {
        for (T obj : objs){
            save(obj);
        }
    }

    public static <T>
    long update(T obj) throws Exception {
        Class clazz = obj.getClass();
        Field[] fields = clazz.getDeclaredFields();
        String class_name = clazz.getName();
        String sql = "update " + class_name.toLowerCase(Locale.ROOT) + " set ";

        String where = " where 1=1 and ";
        Object id_val = null;
        List<Object> param = new ArrayList<>();
        boolean has_id = false;

        for (Field f : fields){
            String f_name = f.getName();
            f.setAccessible(true);
            Object val = null;
            if(f.isAnnotationPresent(Id.class)){
                where += f_name + "= ?";
                try {
                    val = f.get(obj);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
                if(val == null){
                    throw new Exception("id can not be null!");
                }
                id_val = val;
                has_id = true;
                continue;
            }
            try {
                val = f.get(obj);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if(val == null){
                continue;
            }
            param.add(val);
            sql += f_name + "=? , ";
        }

        if(!has_id){
            throw new Exception("Id annotation should be present on id filed!");
        }
        param.add(id_val);

        sql = sql.substring(0, sql.lastIndexOf(','));
        sql += where;

        Connection conn = null;
        PreparedStatement pstmt = null;
        boolean res = true;
        int row = 0;
        try {
            //调用链接方法获取与数据库的链接
            conn = getConnection();
            //利用传入的sql语句创建一个pstmt对象，以便于我们把传入的数组里的数据放到sql语句中
            pstmt = conn.prepareStatement(sql);

            if (!param.isEmpty()){
                for (int i = 0; i < param.size(); i++) {
                    pstmt.setObject(i+1, param.get(i));
                }
            }

            String final_sql = pstmt.toString();
            System.out.println(final_sql);

            res = pstmt.execute();

            if(!res) {
                row = pstmt.getUpdateCount();
            }
            pstmt.close();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return row;
    }

    public static List<Map<String, Object>> execSql(String sql, Object ...param){
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        boolean has_result = false;
        List<Map<String, Object>> result = new ArrayList<>();

        try {
            //调用链接方法获取与数据库的链接
            conn = getConnection();
            //利用传入的sql语句创建一个pstmt对象，以便于我们把传入的数组里的数据放到sql语句中
            pstmt = conn.prepareStatement(sql);
            if (param != null){
                for (int i = 0; i < param.length; i++) {
                    pstmt.setObject(i+1, param[i]);
                }
            }

            has_result = pstmt.execute();
            if(has_result){
                rs = pstmt.getResultSet();

                //通过next方法遍历rs
                while(rs.next()){
                    Map<String, Object> result_map = new HashMap<>();
                    ResultSetMetaData metaData = rs.getMetaData();//获取标的元数据，也就是 表的行
                    //一行有很多列，用for遍历列getColumnCont方法是获取有多少列的
                    for (int i = 0; i < metaData.getColumnCount(); i++) {
                        //getColumnName（）方法，括号是从1开始的列，每次循环i++，这个方法是获取下标所对应的列名
                        String columnName = metaData.getColumnName(i+1);//元数据metadate中的数据也是从1开始，不要忘i是0所以要加一
                        Object object = rs.getObject(columnName);//根据列明从rs结果集中获取数据
                        result_map.put(columnName, object);
                    }
                    result.add(result_map);
                }
                rs.close();
                pstmt.close();
            } else {
                return result;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }

        return result;
    }



}